package android.slc.commonlibrary;


import android.slc.commonlibrary.constants.CacheConstants;
import cn.hutool.cache.impl.LRUCache;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Filter;
import io.reactivex.rxjava3.annotations.NonNull;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * <pre>
 *     author: slc
 *     time  : 2024/08/20
 *     desc  : utils about double cache；没有实现Serializable的类不会存储到文件缓存中
 * </pre>
 */
public class CacheDoubleSyncUtils implements CacheConstants {

    protected static final int DEFAULT_MAX_COUNT = 256;

    protected static final Map<String, CacheDoubleSyncUtils> CACHE_MAP = new HashMap<>();

    private final String mCacheKey;
    private final LRUCache<String, CacheValue> mMemoryCache;
    private final CacheDiskUtils mCacheDiskUtils;

    /**
     * Return the single {@link CacheDoubleSyncUtils} instance.
     *
     * @return the single {@link CacheDoubleSyncUtils} instance
     */
    public static CacheDoubleSyncUtils getInstance() {
        return getInstance(DEFAULT_MAX_COUNT);
    }

    /**
     * Return the single {@link CacheDoubleSyncUtils} instance.
     *
     * @param maxCount The max count of cache.
     * @return the single {@link CacheDoubleSyncUtils} instance
     */
    public static CacheDoubleSyncUtils getInstance(final int maxCount) {
        return getInstance(String.valueOf(maxCount), maxCount);
    }

    /**
     * Return the single {@link CacheDoubleSyncUtils} instance.
     *
     * @param cacheKey The key of cache.
     * @param maxCount The max count of cache.
     * @return the single {@link CacheDoubleSyncUtils} instance
     */
    public static CacheDoubleSyncUtils getInstance(final String cacheKey, final int maxCount) {
        File file = new File(System.getProperty("user.home") + File.separator + "cache", cacheKey);
        return getInstance(cacheKey, file, maxCount);
    }

    public static CacheDoubleSyncUtils getInstance(File cacheDir, final int maxCount) {
        return getInstance(String.valueOf(maxCount), cacheDir, maxCount);
    }

    public static CacheDoubleSyncUtils getInstance(final String cacheKey, File cacheDir, final int maxCount) {
        CacheDoubleSyncUtils cache = CACHE_MAP.get(cacheKey);
        if (cache == null) {
            synchronized (CacheDoubleSyncUtils.class) {
                cache = CACHE_MAP.get(cacheKey);
                if (cache == null) {
                    CacheDiskUtils cacheDiskUtils = CacheDiskUtils.getInstance(cacheDir, Long.MAX_VALUE, maxCount);
                    cache = new CacheDoubleSyncUtils(cacheKey, new LRUCache<>(maxCount), cacheDiskUtils);
                    CACHE_MAP.put(cacheKey, cache);
                }
            }
        }
        return cache;
    }

    protected CacheDoubleSyncUtils(String cacheKey, LRUCache<String, CacheValue> memoryCache, CacheDiskUtils cacheDiskUtils) {
        mCacheKey = cacheKey;
        mMemoryCache = memoryCache;
        mCacheDiskUtils = cacheDiskUtils;
    }

    @Override
    public String toString() {
        return mCacheKey + "@" + Integer.toHexString(hashCode());
    }

    /**
     * Put bytes in cache.
     *
     * @param key   The key of cache.
     * @param value The value of cache.
     */
    public void put(@NonNull final String key, final Object value) {
        put(key, value, -1);
    }

    /**
     * Put bytes in cache.
     *
     * @param key      The key of cache.
     * @param value    The value of cache.
     * @param saveTime The save time of cache, in seconds.
     */
    public void put(@NonNull final String key, final Object value, int saveTime) {
        put(key, value, saveTime, false);
    }

    public void put(@NonNull final String key, final Object value, int saveTime, boolean saveDisk) {
        if (value == null) return;
        long dueTime = saveTime < 0 ? -1 : System.currentTimeMillis() + saveTime * 1000L;
        CacheValue cacheValue = new CacheValue(dueTime, value);
        mMemoryCache.put(key, cacheValue);
        if (saveDisk && value instanceof Serializable) {
            mCacheDiskUtils.put(key, cacheValue);
        }
    }

    /**
     * Return the value in cache.
     *
     * @param key The key of cache.
     * @param <T> The value type.
     * @return the value if cache exists or null otherwise
     */
    public <T> T get(@NonNull final String key) {
        return get(key, null);
    }

    /**
     * Return the value in cache.
     *
     * @param key          The key of cache.
     * @param defaultValue The default value if the cache doesn't exist.
     * @param <T>          The value type.
     * @return the value if cache exists or defaultValue otherwise
     */
    public <T extends Serializable> T get(@NonNull final String key, final T defaultValue) {
        CacheValue val = mMemoryCache.get(key);
        if (val == null) {
            //从本地缓存中找
            val = (CacheValue) mCacheDiskUtils.getSerializable(key);
            //找到了添加到内存缓存中
            if (val != null) {
                mMemoryCache.put(key, val);
            }
        }
        if (val == null) return defaultValue;
        if (val.dueTime == -1 || val.dueTime >= System.currentTimeMillis()) {
            //noinspection unchecked
            return (T) val.value;
        }
        mMemoryCache.remove(key);
        mCacheDiskUtils.remove(key);
        return defaultValue;
    }

    /**
     * Return the count of cache.
     *
     * @return the count of cache
     */
    public int getCacheCount() {
        return mMemoryCache.size();
    }

    /**
     * Remove the cache by key.
     *
     * @param key The key of cache.
     * @return {@code true}: success<br>{@code false}: fail
     */
    public Object remove(@NonNull final String key) {
        CacheValue remove = mMemoryCache.get(key);
        mMemoryCache.remove(key);
        mCacheDiskUtils.remove(key);
        if (remove == null) return null;
        return remove.value;
    }

    /**
     * Clear all of the cache.
     */
    public void clear() {
        mMemoryCache.clear();
        mCacheDiskUtils.clear();
    }

    public static final class CacheValue implements Serializable {
        long dueTime;
        Object value;

        CacheValue(long dueTime, Object value) {
            this.dueTime = dueTime;
            this.value = value;
        }

        public void setValue(Object value) {
            this.value = value;
        }
    }

    public static final long NEVER_EXPIRE = -1L;
    public static final long NOT_VALUE_EXPIRE = -2L;

    protected CacheValue getCache(@NonNull final String key) {
        CacheValue val = mMemoryCache.get(key);
        if (val == null) {
            //从本地缓存中找
            val = (CacheValue) mCacheDiskUtils.getSerializable(key);
            //找到了添加到内存缓存中
            if (val != null) {
                mMemoryCache.put(key, val);
            }
        }
        if (val == null) return null;

        if (val.dueTime == -1 || val.dueTime >= System.currentTimeMillis()) {
            //noinspection unchecked
            return val;
        }
        mMemoryCache.remove(key);
        mCacheDiskUtils.remove(key);
        return null;
    }

    protected void setAndKeepTTL(@NonNull final String key, final Object value) {
        CacheValue cacheValue = getCache(key);
        if (cacheValue == null) {
            return;
        }
        cacheValue.setValue(value);
        if (value instanceof Serializable) {
            mCacheDiskUtils.put(key, cacheValue);
        }
    }

    protected long getTimeToLive(@NonNull final String key) {
        CacheValue cacheValue = getCache(key);
        if (cacheValue == null) {
            return NOT_VALUE_EXPIRE;
        }
        if (cacheValue.dueTime == NEVER_EXPIRE) {
            return NEVER_EXPIRE;
        }
        long currentTime = System.currentTimeMillis();
        if (cacheValue.dueTime > currentTime) {
            return (cacheValue.dueTime - currentTime) * 1000;
        }
        return 0;
    }


    protected Collection<String> keys(Filter<String> filter) {
        Collection<String> keys = CollectionUtil.filterNew(mMemoryCache.keySet(), filter);
        return keys;
    }

    protected boolean hasKey(final String keyPattern) {
        return mMemoryCache.containsKey(keyPattern);
    }

    protected Collection<String> keysByPrefix(final String prefix) {
        return keys(new Filter<String>() {
            @Override
            public boolean accept(String input) {
                return StringUtils.startsWith(input, prefix);
            }
        });
    }
}
